<?php

declare(strict_types=1);

namespace xpzhu\Jwt;

use Lcobucci\JWT\Token;
use xpzhu\Jwt\Exception\JwtException;
use xpzhu\Jwt\Exception\TokenExpiredException;
use xpzhu\Jwt\Exception\TokenInvalidException;

class JwtAuth
{
    /**
     * @var Config
     */
    protected Config $config;

    /**
     * @var array
     */
    protected array $jwtConfig = [];

    /**
     * @var Manager
     */
    protected Manager $manager;

    /**
     * @var Jwt
     */
    protected Jwt $jwt;

    /**
     * @var string
     */
    protected string $store;

    /**
     * @var string
     */
    protected string $defaultStore = 'default';

    /**
     * JwtAuth constructor.
     * @param string $store 应用名称
     */
    public function __construct(string $store = '')
    {
        $config = config('plugin.xpzhu.jwt.app');

        $stores = $config['stores'];
        foreach ($stores as $key => $value) {
            $this->jwtConfig[$key] = $value;
        }

        $this->store  = $store ?? $this->getDefaultApp();
        $this->config = $this->getConfig();

        $this->init();
    }

    protected function init()
    {
        $this->jwt     = new Jwt($this, $this->config);
        $this->manager = new Manager($this->jwt);
    }

    /**
     * 获取应用配置
     * @return Config
     */
    public function getConfig(): Config
    {
        $options = $this->jwtConfig[$this->store];

        if (!empty($options)) {
            return new Config($options);
        } else {
            throw new JwtException($this->store . '应用令牌配置不完整', 4000);
        }
    }

    public function store(string $store = ''): self
    {
        if ($store) {
            $this->store = $store;
        }
        return $this;
    }

    /**
     * 获取应用
     * @return string
     */
    public function getStore(): string
    {
        return $this->store ?? $this->getDefaultApp();
    }

    /**
     * 获取默认应用
     * @return string
     */
    protected function getDefaultApp(): string
    {
        return $this->defaultStore;
    }

    /**
     * 生成 Token
     * @param mixed $identifier
     * @param array $claims
     * @return Token
     */
    public function token($identifier, array $claims = []): Token
    {
        $token = $this->jwt->make($identifier, $claims);
        $this->manager->login($token);
        return $token;
    }

    /**
     * 验证 Token
     * @param string $token
     * @return bool
     */
    public function verify(string $token = ''): bool
    {
        if (!$token) {
            $token = $this->jwt->getRequestToken();
        }

        // 是否存在黑名单
        $this->wasBan($token);

        // 检测合法性
        if (false === $this->jwt->validate($token)) {
            throw new TokenInvalidException('令牌无效', 4000);
        }

        // 过期检测
        if (false === $this->jwt->validateExp($token)) {
            if (false === $this->jwt->isRefreshExpired($token)) {
                throw new TokenExpiredException('刷新令牌过期，请重新登录', 4001);
            }
            throw new TokenExpiredException('令牌过期，请重新获取', 4002);
        }

        return true;
    }

    /**
     * 是否存在黑名单
     * @param string $token
     */
    public function wasBan(string $token)
    {
        $token = $this->jwt->parse($token);
        if (true === $this->manager->wasBan($token)) {
            throw new TokenExpiredException('令牌已经在黑名单', 4003);
        }
    }

    /**
     * 登出
     * @param string $token
     */
    public function logout(string $token = '')
    {
        $token = $token ?: $this->jwt->getRequestToken();
        $token = $this->jwt->parse($token);
        $this->manager->logout($token);
    }

    public function getClaims()
    {
        return $this->jwt->getClaims();
    }

    public function getClaim($name)
    {
        return $this->jwt->getClaim($name);
    }

    /**
     * 注销指定应用下缓存的用户 （强制下线 重新登录）
     * @param string $store 应用名称
     */
    public function destroyStoreWhitelist(string $store = '')
    {
        empty($store) && $store = $this->getStore();
        $this->manager->destroyStoreWhitelist($store);
    }

    public function destroyStoreBlacklist(string $store = '')
    {
        empty($store) && $store = $this->getStore();
        $this->manager->destroyStoreBlacklist($store);
    }

    /**
     * 注销指定某个用户（强制下线 重新登录）
     * @param mixed  $id
     * @param string $store 应用名称
     */
    public function destroyToken($id, string $store = '')
    {
        empty($store) && $store = $this->getStore();
        $this->manager->destroyToken($id, $store);
    }
}
